# Faraday Penetration Test IDE
# Copyright (C) 2016  Infobyte LLC (http://www.infobytesec.com/)
# See the file 'doc/LICENSE' for the license information
import time
import threading

from flask import Blueprint
from filteralchemy import (
    FilterSet,
    operators,
)
from marshmallow import fields, ValidationError, Schema, post_load
from marshmallow.validate import OneOf

import server.utils.logger

from server.api.base import (
    AutoSchema,
    FilterAlchemyMixin,
    FilterSetMeta,
    PaginatedMixin,
    ReadWriteView,
)
from server.models import VulnerabilityTemplate, Vulnerability
from server.schemas import PrimaryKeyRelatedField, SeverityField, SelfNestedField

vulnerability_template_api = Blueprint('vulnerability_template_api', __name__)
logger = server.utils.logger.get_logger(__name__)


class ImpactSchema(Schema):
    accountability = fields.Boolean(attribute='impact_accountability', default=False)
    availability = fields.Boolean(attribute='impact_availability', default=False)
    confidentiality = fields.Boolean(attribute='impact_confidentiality', default=False)
    integrity = fields.Boolean(attribute='impact_integrity', default=False)


class VulnerabilityTemplateSchema(AutoSchema):
    _id = fields.Integer(dump_only=True, attribute='id')
    id = fields.Integer(dump_only=True, attribute='id')
    _rev = fields.String(default='', dump_only=True)
    cwe = fields.String(dump_only=True, default='') # deprecated field, the legacy data is added to refs on import
    exploitation = SeverityField(attribute='severity', required=True)
    references = fields.Method('get_references', deserialize='load_references', required=True)
    refs = fields.List(fields.String(), dump_only=True, attribute='references')
    desc = fields.String(dump_only=True, attribute='description')
    data = fields.String(attribute='data')
    impact = SelfNestedField(ImpactSchema())
    easeofresolution = fields.String(
        attribute='ease_of_resolution',
        validate=OneOf(Vulnerability.EASE_OF_RESOLUTIONS),
        allow_none=True)
    policyviolations = fields.List(fields.String,
                                   attribute='policy_violations')

    class Meta:
        model = VulnerabilityTemplate
        fields = ('id', '_id', '_rev', 'cwe', 'description', 'desc',
                  'exploitation', 'name', 'references', 'refs', 'resolution',
                  'impact', 'easeofresolution', 'policyviolations', 'data')

    def get_references(self, obj):
        return ', '.join(map(lambda ref_tmpl: ref_tmpl.name, obj.reference_template_instances))

    def load_references(self, value):
        if isinstance(value, list):
            references = value
        elif isinstance(value, (unicode, str)):
            if len(value) == 0:
                # Required because "".split(",") == [""]
                return []
            references = [ref.strip() for ref in value.split(',')]
        else:
            raise ValidationError('references must be a either a string '
                                  'or a list')
        if any(len(ref) == 0 for ref in references):
            raise ValidationError('Empty name detected in reference')
        return references

    @post_load
    def post_load_impact(self, data):
        # Unflatten impact (move data[impact][*] to data[*])
        impact = data.pop('impact', None)
        if impact:
            data.update(impact)
        return data


class VulnerabilityTemplateFilterSet(FilterSet):
    class Meta(FilterSetMeta):
        model = VulnerabilityTemplate  # It has all the fields
        fields = (
            'severity')
        operators = (operators.Equal,)


lock = threading.Lock()


class VulnerabilityTemplateView(PaginatedMixin,
                                FilterAlchemyMixin,
                                ReadWriteView):
    route_base = 'vulnerability_template'
    model_class = VulnerabilityTemplate
    schema_class = VulnerabilityTemplateSchema
    filterset_class = VulnerabilityTemplateFilterSet

    def _envelope_list(self, objects, pagination_metadata=None):
        vuln_tpls = []
        for template in objects:
            vuln_tpls.append({
                'id': template['_id'],
                'key': template['_id'],
                'value': {'rev': ''},
                'doc': template
            })
        return {
            'rows': vuln_tpls,
            'total_rows': len(objects)
        }

    def post(self, **kwargs):
        with lock:
            return super(VulnerabilityTemplateView, self).post(**kwargs)


VulnerabilityTemplateView.register(vulnerability_template_api)
